/**
 * Heterotopia - an up-to-date Java-based MUD system
 * Copyright (C) 2011 Bill Garrett <memesis@gmail.com>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package heterotopia.oa;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;

import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;

import org.springframework.beans.factory.InitializingBean;

/**
 * Initializes one or more ScriptEngine instances.
 * Engines are dynamically instantiated to process files found in a directory.
 * The script engine servicing a given file is determined by the file extension.
 * 
 * Example usage in Spring:
 * 
 *  <bean id="scriptEngineFactory" class="heterotopia.ScriptEngineFactory">
 *  	<property name="scriptSource" value="test.rb" />
 *  	<property name="engineReferences">
 *  		<map>
 *  			<entry key="minaHelper">
 *  				<ref local="minaHelper" />
 *  			</entry>
 *  			<entry key="minaHelper2">
 *  				<value>42</value>
 *  			</entry>
 *  		</map>
 *  	</property>
 *  </bean>
 * 
 * @author garrett
 */
public class ScriptRepository implements InitializingBean {
	private ScriptEngineManager manager;
	private Map<String, ScriptEngine> scriptEngineCache;
	private String scriptSource;
	private Map<String, Object> engineGlobals;

	public ScriptRepository() {
		manager = new ScriptEngineManager();
		scriptEngineCache = new HashMap<String, ScriptEngine>();
		engineGlobals = new HashMap<String, Object>();
	}

	/**
	 * @return the scriptFolder
	 */
	public String getScriptSource() {
		return scriptSource;
	}

	/**
	 * @param scriptSource the scriptFolder to set
	 * @throws ScriptException 
	 * @throws FileNotFoundException 
	 */
	public void setScriptSource(String scriptSource) {
		this.scriptSource = scriptSource;
	}
	
	/**
	 * @return the engineGlobals
	 */
	public Map<String, Object> getEngineGlobals() {
		return engineGlobals;
	}

	/**
	 * @param engineGlobals the engineReferences to set
	 */
	public void setEngineGlobals(Map<String, Object> engineGlobals) {
		this.engineGlobals = engineGlobals;
		// If the list of globals has been set after engines are created,
		// re-inject the globals. javax.script does not have any way
		// to remove keys, so we'll have to trust that this is okay.
		for(ScriptEngine engine: scriptEngineCache.values()) {
			registerEngineGlobals(engine);
		}
	}

	/**
	 * This method is called by Spring after all properties have been set.
	 * At this point we read from the scriptSource (either a file or
	 * directory path) and include any script files we find there.
	 * Registered globals are injected into an engine as it is created.
	 */
	public void afterPropertiesSet() throws Exception {
		if(scriptSource == null)
			return;

		File dir = new File(scriptSource);
		if(dir.isFile())
			addFileToScriptEngine(dir);
		else if(dir.isDirectory()) {
			for(File f: dir.listFiles()) {
				if(f.isFile())
					addFileToScriptEngine(f);
			}
		}
	}

	private ScriptEngine findOrCreateByExtension(String extension) {
		if(scriptEngineCache.containsKey(extension))
			return scriptEngineCache.get(extension);
		
		ScriptEngine engine = manager.getEngineByExtension(extension);
		if(engine == null)
			return null;

		registerEngineGlobals(engine);

		scriptEngineCache.put(extension, engine);
		return engine;
	}
	
	private void addFileToScriptEngine(File file) throws FileNotFoundException, ScriptException {
		String extension = getExtension(file.getName());
		ScriptEngine engine = findOrCreateByExtension(extension);
		if(engine == null)	// TODO: log this
			return;
		
		engine.eval(new FileReader(file));
	}
	
	private static String getExtension(String filepath) {
        String ext = null;
        int i = filepath.lastIndexOf('.');

        if (i > 0 &&  i < filepath.length() - 1) {
            ext = filepath.substring(i+1).toLowerCase();
        }
        return ext;
    }
	
	/**
	 * For a given script engine, register any globals that are present.
	 * 
	 * @param engine the ScriptEngine
	 */
	private void registerEngineGlobals(ScriptEngine engine) {
		if(engineGlobals == null)
			return;

		for(Entry<String, Object> entry: engineGlobals.entrySet()) {
			engine.put(entry.getKey(), entry.getValue());
		}
	}

	public void eval(Map<String, String> msg) {
		String command = msg.remove("command");
		if(!msg.containsKey("sessionLifecycle")) {
			try {
				ScriptEngine engine = findOrCreateByExtension("rb");
				if(engine == null)
					msg.put("text", "No such engine.");
				else {
					Object o = engine.eval(command);
					msg.put("text", "=> " + o);
				}
			} catch (Exception e) {
				msg.put("text", e.toString());
			}
		}
	}
}
